/* kernel.c - the C part of the kernel */
/* Copyright (C) 1999, 2010  Free Software Foundation, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "multiboot2.h"

/* Macros.  */

/* Some screen stuff.  */
/* The number of columns.  */
#define COLUMNS			80
/* The number of lines.  */
#define LINES			24
/* The attribute of an character.  */
#define ATTRIBUTE		7
/* The video memory address.  */
#define VIDEO			0xB8000

/* Variables.  */
/* Save the X position.  */
static int xpos;
/* Save the Y position.  */
static int ypos;
/* Point to the video memory.  */
static volatile unsigned char *video;

/* Forward declarations.  */
void cmain (unsigned long magic, unsigned long addr);
static void cls (void);
static void itoa (char *buf, int base, int d);
static void putchar (int c);
void printf (const char *format, ...);

/* Check if MAGIC is valid and print the Multiboot information structure
   pointed by ADDR.  */
void
cmain (unsigned long magic, unsigned long addr)
{  
  struct multiboot_tag *tag;
  unsigned size;

  /* Clear the screen.  */
  cls ();

  /* Am I booted by a Multiboot-compliant boot loader?  */
  if (magic != MULTIBOOT2_BOOTLOADER_MAGIC)
    {
      printf ("Invalid magic number: 0x%x\n", (unsigned) magic);
      return;
    }

  if (addr & 7)
    {
      printf ("Unaligned mbi: 0x%x\n", addr);
      return;
    }

  size = *(unsigned *) addr;
  printf ("Announced mbi size 0x%x\n", size);
  for (tag = (struct multiboot_tag *) (addr + 8);
       tag->type != MULTIBOOT_TAG_TYPE_END;
       tag = (struct multiboot_tag *) ((multiboot_uint8_t *) tag 
				       + ((tag->size + 7) & ~7)))
    {
      printf ("Tag 0x%x, Size 0x%x\n", tag->type, tag->size);
      switch (tag->type)
	{
	case MULTIBOOT_TAG_TYPE_CMDLINE:
	  printf ("Command line = %s\n",
		  ((struct multiboot_tag_string *) tag)->string);
	  break;
	case MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME:
	  printf ("Boot loader name = %s\n",
		  ((struct multiboot_tag_string *) tag)->string);
	  break;
	case MULTIBOOT_TAG_TYPE_MODULE:
	  printf ("Module at 0x%x-0x%x. Command line %s\n",
		  ((struct multiboot_tag_module *) tag)->mod_start,
		  ((struct multiboot_tag_module *) tag)->mod_end,
		  ((struct multiboot_tag_module *) tag)->cmdline);
	  break;
	case MULTIBOOT_TAG_TYPE_BASIC_MEMINFO:
	  printf ("mem_lower = %uKB, mem_upper = %uKB\n",
		  ((struct multiboot_tag_basic_meminfo *) tag)->mem_lower,
		  ((struct multiboot_tag_basic_meminfo *) tag)->mem_upper);
	  break;
	case MULTIBOOT_TAG_TYPE_BOOTDEV:
	  printf ("Boot device 0x%x,%u,%u\n",
		  ((struct multiboot_tag_bootdev *) tag)->biosdev,
		  ((struct multiboot_tag_bootdev *) tag)->slice,
		  ((struct multiboot_tag_bootdev *) tag)->part);
	  break;
	case MULTIBOOT_TAG_TYPE_MMAP:
	  {
	    multiboot_memory_map_t *mmap;

	    printf ("mmap\n");
      
	    for (mmap = ((struct multiboot_tag_mmap *) tag)->entries;
		 (multiboot_uint8_t *) mmap 
		   < (multiboot_uint8_t *) tag + tag->size;
		 mmap = (multiboot_memory_map_t *) 
		   ((unsigned long) mmap
		    + ((struct multiboot_tag_mmap *) tag)->entry_size))
	      printf (" base_addr = 0x%x%x,"
		      " length = 0x%x%x, type = 0x%x\n",
		      (unsigned) (mmap->addr >> 32),
		      (unsigned) (mmap->addr & 0xffffffff),
		      (unsigned) (mmap->len >> 32),
		      (unsigned) (mmap->len & 0xffffffff),
		      (unsigned) mmap->type);
	  }
	  break;
	case MULTIBOOT_TAG_TYPE_FRAMEBUFFER:
	  {
	    multiboot_uint32_t color;
	    unsigned i;
	    struct multiboot_tag_framebuffer *tagfb
	      = (struct multiboot_tag_framebuffer *) tag;
	    void *fb = (void *) (unsigned long) tagfb->common.framebuffer_addr;

	    switch (tagfb->common.framebuffer_type)
	      {
	      case MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED:
		{
		  unsigned best_distance, distance;
		  struct multiboot_color *palette;
	    
		  palette = tagfb->framebuffer_palette;

		  color = 0;
		  best_distance = 4*256*256;
	    
		  for (i = 0; i < tagfb->framebuffer_palette_num_colors; i++)
		    {
		      distance = (0xff - palette[i].blue) 
			* (0xff - palette[i].blue)
			+ palette[i].red * palette[i].red
			+ palette[i].green * palette[i].green;
		      if (distance < best_distance)
			{
			  color = i;
			  best_distance = distance;
			}
		    }
		}
		break;

	      case MULTIBOOT_FRAMEBUFFER_TYPE_RGB:
		color = ((1 << tagfb->framebuffer_blue_mask_size) - 1) 
		  << tagfb->framebuffer_blue_field_position;
		break;

	      case MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT:
		color = '\\' | 0x0100;
		break;

	      default:
		color = 0xffffffff;
		break;
	      }
	    
	    for (i = 0; i < tagfb->common.framebuffer_width
		   && i < tagfb->common.framebuffer_height; i++)
	      {
		switch (tagfb->common.framebuffer_bpp)
		  {
		  case 8:
		    {
		      multiboot_uint8_t *pixel = fb
			+ tagfb->common.framebuffer_pitch * i + i;
		      *pixel = color;
		    }
		    break;
		  case 15:
		  case 16:
		    {
		      multiboot_uint16_t *pixel
			= fb + tagfb->common.framebuffer_pitch * i + 2 * i;
		      *pixel = color;
		    }
		    break;
		  case 24:
		    {
		      multiboot_uint32_t *pixel
			= fb + tagfb->common.framebuffer_pitch * i + 3 * i;
		      *pixel = (color & 0xffffff) | (*pixel & 0xff000000);
		    }
		    break;

		  case 32:
		    {
		      multiboot_uint32_t *pixel
			= fb + tagfb->common.framebuffer_pitch * i + 4 * i;
		      *pixel = color;
		    }
		    break;
		  }
	      }
	    break;
	  }

	}
    }
  tag = (struct multiboot_tag *) ((multiboot_uint8_t *) tag 
				  + ((tag->size + 7) & ~7));
  printf ("Total mbi size 0x%x\n", (unsigned) tag - addr);
}    

/* Clear the screen and initialize VIDEO, XPOS and YPOS.  */
static void
cls (void)
{
  int i;

  video = (unsigned char *) VIDEO;
  
  for (i = 0; i < COLUMNS * LINES * 2; i++)
    *(video + i) = 0;

  xpos = 0;
  ypos = 0;
}

/* Convert the integer D to a string and save the string in BUF. If
   BASE is equal to 'd', interpret that D is decimal, and if BASE is
   equal to 'x', interpret that D is hexadecimal.  */
static void
itoa (char *buf, int base, int d)
{
  char *p = buf;
  char *p1, *p2;
  unsigned long ud = d;
  int divisor = 10;
  
  /* If %d is specified and D is minus, put `-' in the head.  */
  if (base == 'd' && d < 0)
    {
      *p++ = '-';
      buf++;
      ud = -d;
    }
  else if (base == 'x')
    divisor = 16;

  /* Divide UD by DIVISOR until UD == 0.  */
  do
    {
      int remainder = ud % divisor;
      
      *p++ = (remainder < 10) ? remainder + '0' : remainder + 'a' - 10;
    }
  while (ud /= divisor);

  /* Terminate BUF.  */
  *p = 0;
  
  /* Reverse BUF.  */
  p1 = buf;
  p2 = p - 1;
  while (p1 < p2)
    {
      char tmp = *p1;
      *p1 = *p2;
      *p2 = tmp;
      p1++;
      p2--;
    }
}

/* Put the character C on the screen.  */
static void
putchar (int c)
{
  if (c == '\n' || c == '\r')
    {
    newline:
      xpos = 0;
      ypos++;
      if (ypos >= LINES)
	ypos = 0;
      return;
    }

  *(video + (xpos + ypos * COLUMNS) * 2) = c & 0xFF;
  *(video + (xpos + ypos * COLUMNS) * 2 + 1) = ATTRIBUTE;

  xpos++;
  if (xpos >= COLUMNS)
    goto newline;
}

/* Format a string and print it on the screen, just like the libc
   function printf.  */
void
printf (const char *format, ...)
{
  char **arg = (char **) &format;
  int c;
  char buf[20];

  arg++;
  
  while ((c = *format++) != 0)
    {
      if (c != '%')
	putchar (c);
      else
	{
	  char *p, *p2;
	  int pad0 = 0, pad = 0;
	  
	  c = *format++;
	  if (c == '0')
	    {
	      pad0 = 1;
	      c = *format++;
	    }

	  if (c >= '0' && c <= '9')
	    {
	      pad = c - '0';
	      c = *format++;
	    }

	  switch (c)
	    {
	    case 'd':
	    case 'u':
	    case 'x':
	      itoa (buf, c, *((int *) arg++));
	      p = buf;
	      goto string;
	      break;

	    case 's':
	      p = *arg++;
	      if (! p)
		p = "(null)";

	    string:
	      for (p2 = p; *p2; p2++);
	      for (; p2 < p + pad; p2++)
		putchar (pad0 ? '0' : ' ');
	      while (*p)
		putchar (*p++);
	      break;

	    default:
	      putchar (*((int *) arg++));
	      break;
	    }
	}
    }
}
